﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using com.opttek.optquest;
using System.Globalization;
using System.Threading;
using System.Windows.Forms;

namespace ConsoleApplication1
{
    class MainProgram
    {


        private static MainProgram instance;
        public string output = "";
        public int license;
        public string rootpath;
        public string resultspath;
        public bool setupcost;
        public int stopiterations;
        public bool useiterations;
        public int stopmin;
        public int bincount;
        public int evaluators;

        public DateTime basestart = DateTime.Now;
        public  KCOptQuest optquest;
        public  SimData mydata;
        public MainForm myform;


        public static MainProgram getInstance()
        {
            if (instance == null)
                instance = new MainProgram();
            return instance;
        }

        private MainProgram()
        {

        }

        [STAThread]
        static void Main(string[] args)
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            MainProgram program = MainProgram.getInstance();
            program.myform = new MainForm();
            try
            {
                Application.Run(program.myform);
            }
            catch {
                Application.Exit();
           }
        }

        public static void WriteLine(String s)
        {
                    
            MainProgram i = MainProgram.getInstance();
            i.output += s;
            i.output += System.Environment.NewLine;
            ProgressPacket packet = new ProgressPacket(true);
            packet.output = i.output;
            i.output = "";
            i.myform.worker.ReportProgress(0, packet);
          
        }

        public void SaveConfig(FormConfig config)
        {
            license = config.license;
            rootpath = config.rootpath;
            resultspath = config.resultspath;
            evaluators = config.evaluators;
            setupcost = config.setupcost;
            useiterations = config.useriterations;
            stopiterations = config.stopiterations;
            stopmin = config.stopmins;
            bincount = config.bincount;
        }

        public void Execute()
        {
           Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");

            WriteLine("Starting Program\n");
            WriteLine("Reading Data\n");

                mydata = new SimData(rootpath, bincount);



            WriteLine("Generating Optquest\n");
            optquest = new KCOptQuest(license, mydata, evaluators);

            optquest.SetMetaHeuristic(COptQuestSearchParameters.META_SCATTER);

            
            
            optquest.SetEvaluate(optquest.evaluators);
            WriteLine("Parallel optimization with " + optquest.evaluators + " threads");
            int total = 0;
            total += addVariables();
            total += addRestrictions();


            WriteLine("Total Elements added to Optquest " + total);

            optquest.AddVariable(optquest.salesuc);
            optquest.AddVariable(optquest.prodcostuc);
            optquest.AddVariable(optquest.setupcostuc);
            optquest.AddVariable(optquest.holdcostuc);
            optquest.AddVariable(optquest.backordercostuc);
            optquest.AddVariable(optquest.tarinvcostuc);
            optquest.AddVariable(optquest.serviceleveluc);
            
            optquest.revenueuc.SetEquation("sales - prodcost - setupcost - holdcost - backordercost - tarinvcost");
            optquest.revenueuc.SetMaximize();
            optquest.AddObjective(optquest.revenueuc);
            

            Console.WriteLine("\nStarting Optimization...");

            if (useiterations)
                optquest.SetMaximumIterations(stopiterations);
            else
                optquest.SetMaximumTime(stopmin);
 
            basestart = DateTime.Now;
            optquest.Optimize();
            COptQuestSolution bestsol = optquest.GetBestSolution();
            bool a = bestsol.IsFeasible();
            WriteLine("Feasibility Analysis is " + a);
            WriteLine("\nBest solution found \n");
            WriteLine("Objective function = " + bestsol.GetObjectiveValue());
            //Read horizon from parameters
            TimeSpan finish = DateTime.Now - basestart;
            WriteLine("Total Algorithm runtime :" + finish.TotalSeconds + " seconds");
            Results results = Results.getResultsInstance();
            results.setOpt(optquest);

            results.generateResults(resultspath);
          //  Console.ReadKey();


        }


        private  int addVariables()
        {
            Console.WriteLine("Variables generated:" + optquest.mycube.designVariables.Count + "\n");
            Console.WriteLine("Adding them to Optquest...");
            int i = 0;
            foreach (COptQuestDesignVariable var in optquest.mycube.designVariables.Values)
            {
                i++;
                DateTime start = DateTime.Now;
                optquest.AddVariable(var);
                DateTime end = DateTime.Now;
                TimeSpan span = end - start;
                TimeSpan totalspan = end - basestart;
                if (i % 1000 == 0)
                {
                    Console.WriteLine("Variables Added to Optquest: " + i + ", time:" + totalspan.Seconds + " seconds");
                }
            }
            return i;
        }

        private  int addRestrictions()
        {
            int totalres = 0;
            Console.WriteLine("Adding Restrictions...");

            
         //   if (ConsoleApplication1.Properties.Settings.Default.setup)
                totalres += addSetupConstraints();
           // if (ConsoleApplication1.Settings.Default.machours)
                totalres += addNetHoursConstraints();
           // if (ConsoleApplication1.Settings.Default.BOM)
                totalres += addBomRequirements();
            
            return totalres;
        }


        private  int addBomRequirements()
        {
            int i;
            int k = 0;
            string eq;
            COptQuestStringConstraint bom;
            for (i = 0; i < mydata.horizon; i++)
            {
                foreach (string comp in mydata.mybomcom.Keys)
                {
                    string key = "bom"+ comp + i;
                    optquest.mybomreq.MergeSafe(key, new COptQuestUserControlledVariable(key));
                    optquest.AddVariable(optquest.mybomreq[key]);
                    //optquest.mybomreq.Add(key, req);
                    
                    bom = new COptQuestStringConstraint(key);

                    eq = optquest.mybomreq[key].GetName() + " >= 0";
                    bom.SetEquation(eq);
                    optquest.AddConstraint(bom);
                    k++;

                }
            }
            Console.WriteLine(k + " BOM Requirements have been added");
            return k;

        }


        //private static int addSimultaneousContraints()
        //{
        //    int k = 0;


        //    //Constraints that ensure that only one product at a time is produced in a machine
        //    COptQuestLEConstraint simult;
        //    DesignVariable variable;
        //    foreach (Machine machine in mydata.mymachines.Values)
        //    {
        //        for (int i = 0; i < mydata.horizon; i++)
        //        {
        //            string key = machine.machine + "-" + i;
        //            simult = new COptQuestLEConstraint();
        //            simult.SetName(key);
        //            simult.SetRHS(1);

        //            foreach (String mat in machine.materials.Keys)
        //            {
        //                variable = optquest.mycube.machineRefs[machine.machine][mat][i];
        //                simult.AddVariable(variable, 1);

        //            }
        //            optquest.AddConstraint(simult);
        //            k++;
        //        }
        //    }

        //    Console.WriteLine(k + " Simultaneous Contraints have been added");
        //    return k;
        //}

        private  int addSetupConstraints()
        {

            COptQuestStringConstraint setup;
            int j;
            int k;
            int i;
            int l=0;
            string eq = "";
            string prod2 = "";
            float estimate = mydata.setuprows * mydata.horizon;
            Console.WriteLine("Estimated " + estimate + " setup constraints");
            foreach (Machine machine in mydata.mymachines.Values)
                if (machine.materials.Count > 1)
                    foreach (DesignVariable var in optquest.mycube.getVarsForMachine(machine.machine))
                    {                        
                        int time = var.time;

                            for (i = 0; i<= var.GetUpperBound(); i++)
                            {
                                string key = var.GetName() + i;
                                setup = new COptQuestStringConstraint(key);

                                for (j=0; j <= var.GetUpperBound(); j++)
                                {
                                    if (j!=i)
                                    {
                                        try
                                        {
                                            for (k = time + 1; k < time + mydata.mysetup[machine.machine][var.getMatkey(i)][var.getMatkey(j)].setuptime + 1; k++)
                                            {
                                                if (k < mydata.horizon)
                                                    prod2 += "(" + machine.machine + "_" + k + "-" + j + ")*";
                                            }
                                        }
                                        catch
                                        {
                                            //If key not found, just continue
                                        }
                                    }
                                }
                                if (prod2.Length >= 2)
                                    prod2 = prod2.Remove(prod2.Length - 1);

                                if (prod2.Length == 0)
                                    prod2 = "1";

                                eq = "abs("+ prod2 + ") > 0";
                                setup.SetEquation(eq);
                                optquest.AddConstraint(setup);
                                l++;
                                
                                //setup.CheckExpression();
                                prod2 = "";
                            }
                    }


            Console.WriteLine("In total, " + l + " Setup Contraints have been added");
            return l;

        }

        //private static int addSetupConstraints()
        //{

        //    COptQuestStringConstraint setup;
        //    int j = 0;
        //    int k = 0;
        //    int i;
        //    string auxisuma = "";
        //    string auxi2 = "";
        //    string eq = "";
        //    float estimate = mydata.setuprows * mydata.horizon;
        //    Console.WriteLine("Estimated " + estimate + " setup constraints");
        //    foreach (Machine machine in mydata.mymachines.Values)
        //        if (machine.materials.Count > 1)
        //            for (i = 0; i < mydata.horizon; i++)
        //            {
        //                string key = machine.machine + i;
        //                setup = new COptQuestStringConstraint(key);

        //                foreach (String matfrom in machine.materials.Keys)
        //                    try
        //                    {

        //                        foreach (String matto in mydata.mysetup[machine.machine][matfrom].Keys)
        //                        {
        //                            try
        //                            {
        //                                for (j = i; (j <= i + mydata.mysetup[machine.machine][matfrom][matto].setuptime)
        //                                    && (j < mydata.horizon); j++)
        //                                {
        //                                    auxisuma += machine.machine + matto + "_" + j + " + ";
        //                                }
        //                            }
        //                            catch (KeyNotFoundException)
        //                            {
        //                            }
        //                        }
        //                        if (auxisuma.Length >= 2)
        //                            auxisuma = auxisuma.Remove(auxisuma.Length - 2);
        //                        auxi2 += machine.machine + matfrom + "_" + i + " * (" + auxisuma + ") + ";
        //                        auxisuma = "";
        //                    }
        //                    catch (KeyNotFoundException)
        //                    {
        //                    }
        //                if (auxi2.Length >= 2)
        //                    auxi2 = auxi2.Remove(auxi2.Length - 2);

        //                eq = auxi2 + "<= 0";
        //                setup.SetEquation(eq);
        //                optquest.AddConstraint(setup);
        //                k++;
        //                //setup.CheckExpression();
        //                auxi2 = "";
        //            }


        //    Console.WriteLine("In total, " + k + " Setup Contraints have been added");
        //    return k;

        //}

        private  int addNetHoursConstraints()
        {
            //Machine nethours Constraints            
            string eq = "";
            COptQuestStringConstraint nethours;
            int k = 0;

            foreach (Machine machine in mydata.mymachines.Values)
            {
                foreach (Epoch epoch in mydata.myepochs.Values)
                {
                    if (machine.nethours[epoch.epoch] < epoch.duration)
                    {
                        string key = machine.machine + epoch.epoch;
                        optquest.mysparetimes.Add(key, new COptQuestUserControlledVariable(key)); 
                        optquest.AddVariable(optquest.mysparetimes[key]);

                        nethours = new COptQuestStringConstraint(key);

                        eq = optquest.mysparetimes[key].GetName() + " >= " + Convert.ToString(epoch.duration - machine.nethours[epoch.epoch]);
                        nethours.SetEquation(eq);
                        optquest.AddConstraint(nethours);
                        //nethours.CheckExpression();
                        k++;
                    }
                }
            }

            Console.WriteLine(k + " Nehours Requirements have been added");
            return k;

        }
    }
}
